package gu.sql2java.excel;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static gu.sql2java.SimpleLog.log;
import static gu.sql2java.excel.BaseExcelReader.suffixOf;
import static com.google.common.base.Strings.nullToEmpty;
import static com.gitee.l0km.com4j.basex.bean.BeanPropertySupport.isEmpty;
import static com.google.common.base.MoreObjects.firstNonNull;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import com.google.common.base.Predicates;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.primitives.Ints;

import gu.sql2java.excel.config.SheetConfig;

/**
 * EXCEL(.xls,.xlsx,.csv)文件导入,支持多工作薄导入<br>
 * 
 * @author guyadong
 * @since 3.29.0
 *
 */
public final class ExcelReader implements IExcelReader {
	@SuppressWarnings("rawtypes")
	private final ArrayList<BaseExcelReader> builders = Lists.newArrayList();
	private Collection<Integer> readerWorkSort;
	private Boolean debugOutput;
	/**
	 * 默认构造方法，创建第一个工作薄配置
	 */
	public ExcelReader() {
		this(null,new ArrayList<>(),null);
	}

	/**
	 * 构造方法
	 * 创建第一个工作薄配置
	 * @param beanConsumer
	 */
	public ExcelReader(Consumer<?> beanConsumer) {
		this(null,new ArrayList<>(),beanConsumer);
	}

	/**
	 * 构造方法
	 * 创建第一个工作薄配置
	 * @param sheetConfig
	 * @param beanConsumer
	 */
	public ExcelReader(SheetConfig sheetConfig, Consumer<?> beanConsumer) {
		this(sheetConfig,new ArrayList<>(),beanConsumer);
	}

	/**
	 * 构造方法
	 * 创建第一个工作薄配置
	 * @param sheetConfig
	 * @param rows
	 * @param beanConsumer
	 */
	public ExcelReader(SheetConfig sheetConfig, List<?> rows, Consumer<?> beanConsumer) {
		configSheet(sheetConfig, rows, beanConsumer,0);
	}

	public ExcelReader(SheetConfig sheetConfig) {
		this(sheetConfig,new ArrayList<>(),null);
	}
	public ExcelReader (BaseExcelReader<?> builder) {
		configSheet(builder,0);
	}

	/**
	 * 设置调试输出标志
	 * @param debugOutput
	 */
	public ExcelReader debugOutput(Boolean debugOutput) {
		this.debugOutput = debugOutput;
		return this;
	}
	/**
	 * 打开/关闭嵌套字段优先模式,默认开启
	 * @param subFirst 为{@code true} 嵌套字段优先,否则按输入字段顺序处理
	 * @param indexes 工作薄解析配置对象索引
	 * @return 当前对象
	 * @since 3.29.9
	 */
	public ExcelReader subFirst(boolean subFirst,int...indexes) {
		if(indexes != null) {
			for(int index:indexes) {
				if(index >= 0 && index < builders.size()) {
					getBuilder(index).subFirst(subFirst);
				}
			}
		}
		return this;
	}
	/**
	 * 打开/关闭嵌套字段优先模式,默认开启
	 * @param subFirst 为{@code true} 嵌套字段优先,否则按输入字段顺序处理
	 * @param names 工作薄解析配置对象对应的工作薄名称
	 * @return 当前对象
	 * @since 3.29.9
	 */
	public ExcelReader subFirst(boolean subFirst,String...names) {
		if(names != null) {
			for(String name:names) {
				if(!isEmpty(name)) {
					BaseExcelReader<?> builder = getBuilderOrNull(name);
					if(null != builder) {
						builder.subFirst(subFirst);
					}
				}
			}
		}
		return this;
	}
	/**
	 * 多工作薄导入的情况下，
	 * 指定工作薄解析配置的执行顺序，如果不指定则从0开始顺序执行 
	 * @param indexes
	 * @return 当前对象
	 * @since 3.29.7
	 */
	public ExcelReader readerWorkSort(Iterable<Integer> indexes) {
		if(null != indexes) {
			
			this.readerWorkSort = Lists.newArrayList(Iterables.filter(indexes, Predicates.notNull())) ;
		}
		return this;
	}
	/**
	 * 多工作薄导入的情况下，
	 * 指定工作薄解析配置的执行顺序，如果不指定则从0开始顺序执行 
	 * @param indexes
	 * @return 当前对象
	 * @since 3.29.7
	 */
	public ExcelReader readerWorkSort(int... indexes) {
		if(null != indexes) {
			this.readerWorkSort = Ints.asList(indexes);
		}
		return this;
	}

	/**
	 * 顺序设置工作薄解析配置对象
	 * @param readers
	 * @return 当前对象
	 * @since 3.29.8
	 */
	public ExcelReader configSheet(Iterable<BaseExcelReader<?>> readers) {
		if(null != readers) {
			builders.clear();
			builders.addAll(readers instanceof Collection 
					? (Collection<BaseExcelReader<?>>)readers
					: Lists.newArrayList(readers));
		}
		return this;
	}
	/**
	 * 顺序设置工作薄解析配置对象
	 * @param readers
	 * @return 当前对象
	 * @since 3.29.8
	 */
	public ExcelReader configSheet(BaseExcelReader<?>... readers) {
		if(null != readers) {
			configSheet(Arrays.asList(readers));
		}
		return this;
	}
	/**
	 * 为指定索引设置工作薄解析配置
	 * @param reader
	 * @param index 指定配置索引位置(0-based) 
	 * @return 当前对象
	 * @since 3.29.7
	 */
	public <B extends BaseExcelReader<?>>ExcelReader configSheet(B reader, int index) {
		checkArgument(index>=0,"INVALID index %s",index);
		for(int i = builders.size();i<=index;++i) {
			builders.add(null);
		}
		builders.set(index, checkNotNull(reader,"builder is null"));
		return this;
	}
	/**
	 * 为指定索引设置工作薄解析配置
	 * @param sheetConfig
	 * @param rows 工作薄的解析记录保存列表
	 * @param beanConsumer
	 * @param index 指定配置索引位置(0-based) 
	 * @return 当前对象
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public ExcelReader configSheet(SheetConfig sheetConfig, List<?> rows, Consumer<?> beanConsumer, int index) {
		BaseExcelReader builder = new BaseExcelReader(sheetConfig, rows, beanConsumer);
		configSheet(builder,index);
		return this;
	}
	/**
	 * 为指定索引设置工作薄解析配置
	 * @param sheetConfig
	 * @param beanConsumer
	 * @param index 0-based index
	 * @return 当前对象
	 * @see #configSheet(SheetConfig, List, Consumer, int)
	 */
	public ExcelReader configSheet(SheetConfig sheetConfig,Consumer<?> beanConsumer, int index) {
		configSheet(sheetConfig, new ArrayList<>(), beanConsumer, index);
		return this;
	}
	/**
	 * 为指定索引设置工作薄解析配置
	 * @param sheetConfig
	 * @param index 0-based index
	 * @return 当前对象
	 * @see #configSheet(SheetConfig, List, Consumer, int)
	 */
	public ExcelReader configSheet(SheetConfig sheetConfig, int index) {
		return configSheet(sheetConfig, new ArrayList<>(),null, index);
	}
	/**
	 * 为指定索引设置工作薄解析配置
	 * @param beanConsumer
	 * @param index 0-based index
	 * @return 当前对象
	 * @see #configSheet(SheetConfig, List, Consumer, int)
	 */
	public ExcelReader configSheet(Consumer<?> beanConsumer, int index) {
		return configSheet(new SheetConfig(), new ArrayList<>(),beanConsumer, index);
	}
	/**
	 * 从{@link InputStream}将导入数据记录<br>
	 * 多工作薄导入时，如果解析配置{@link SheetConfig} 指定了工作薄名称(参见 {@code SheetConfig.sheetName }字段 )，优先使用SheetName查找工作薄
	 * 如果没有指定工作薄名，则查找对应的索引获取 Sheet读取数据
	 * @param inputStream
	 * @param format 文件格式,即文件后缀: .xls,xlxs,.cxv
	 * @throws IOException
	 */
	@SuppressWarnings("rawtypes")
	public void read(InputStream inputStream, Charset charset, String format) throws IOException {
		switch(nullToEmpty(format).toLowerCase()) {
		case ".csv":
			new CSVReader(checkNotNull(builders.get(0),"Sheet Config(0) is null"))
				.debugOutput(debugOutput)
				.read(inputStream, charset, format);
		break;
		case ".xls":
		case ".xlsx":{
			try(Workbook workbook = createWorkbook(inputStream, format)){
				if(readerWorkSort == null) {
					/** 如果没有指定工作薄解析配置的执行顺序则从0开始顺序执行 */
					readerWorkSort = ContiguousSet.create(Range.closedOpen(0, builders.size()),
							DiscreteDomain.integers());
				}
				for(Integer i:readerWorkSort) {
					checkArgument(i>=0&&i<builders.size(),"INVALID index %s,>=0 AND < %s required",i,builders.size());
					BaseExcelReader builder = builders.get(i);
					if(null == builder) {
						continue;
					}
					SheetReader sheetReader = builder instanceof SheetReader 
							? (SheetReader)builder
							: new SheetReader(builder);
					sheetReader.debugOutput(debugOutput);
					Sheet sheet = null;
					// 如果指定了工作薄名称，优先使用SheetName查找工作薄
					String sheetName = builder.getSheetConfig().getSheetName();
					if(!"exportedExcel".equals(sheetName) && !isEmpty(sheetName)) {
						sheet  = workbook.getSheet(sheetName);
					}
					if(null == sheet && i < workbook.getNumberOfSheets()) {
						sheet = workbook.getSheetAt(i);
					}
					checkState(null != sheet,"NOT FOUND Sheet for SheetConfig "+i);
					log("READ Sheet name '{}' index= {}",sheetName,i);
					sheetReader.read(sheet);
				}
			}
			break;
		}
		default:
			throw new  IOException("UNSUPPORTED format :"+format);
		}
	}
	static Workbook createWorkbook(InputStream inputStream, String format) throws IOException {
		switch(nullToEmpty(format).toLowerCase()) {
		case ".xls":
			return new HSSFWorkbook(inputStream);
		case ".xlsx":
			return new XSSFWorkbook(inputStream);
		default:
			throw new  IOException("UNSUPPORTED format :"+format);
		}
	}
	/**
	 * 设置第一个工作薄解析配置对象的解析记录保存列表为{@code null}忽略
	 */
	@Override
	public <T> void setRows(List<T> rows) {
		setRows(rows, 0);
	}

	/**
	 * 返回第一个工作薄解析配置对象的解析记录保存列表
	 */
	@Override
	public <T> List<T> getRows() {
		return getRows(0);
	}
	/**
	 * 设置索引指定的工作薄的解析记录保存列表为{@code null}忽略，
	 * 索引无效抛出异常
	 * @param <T>
	 * @param rows
	 * @param index
	 * @return 当前对象
	 */
	public <T> ExcelReader setRows(List<T> rows,int index) {
		getBuilder(index).setRows(rows);
		return this;
	}
	/**
	 * 设置工作薄名指定的工作薄的解析记录保存列表为{@code null}忽略，
	 * 按工作薄名无法找到工作薄则抛出异常
	 * @param <T>
	 * @param rows
	 * @param sheetName 工作薄名
	 * @return 当前对象
	 */
	public <T> ExcelReader setRows(List<T> rows,String sheetName) {
		getBuilder(sheetName).setRows(rows);
		return this;
	}
	
	/**
	 * 返回索引指定的工作薄的解析记录保存列表，
	 * 索引无效抛出异常
	 * @param <T>
	 * @param index
	 */
	public <T> List<T> getRows(int index) {
		return getBuilder(index).getRows();
	}
	/**
	 * 返回工作薄名指定的工作薄的解析记录保存列表，
	 * 按工作薄名无法找到工作薄则抛出异常
	 * @param <T>
	 * @param sheetName
	 */
	public <T> List<T> getRows(String sheetName) {
		return getBuilder(sheetName).getRows();
	}

	private BaseExcelReader<?> getBuilder(int index) {
		try {
			return checkNotNull(this.builders.get(index),"NOT DEFINE Sheet Config at index  "+index);
		} catch (IndexOutOfBoundsException e) {
			throw new IllegalArgumentException("INVALID index "+ index);
		}
	}
	private <T> BaseExcelReader<?> getBuilder(String sheetName) {
		try {
			return Iterables.tryFind(builders, 
					b->null != b && b.sheetConfig.getSheetName().equals(sheetName)).get();
		} catch (IllegalStateException e) {
			throw new IllegalArgumentException("INVALID sheetName "+ sheetName);
		}
	}
	private <T> BaseExcelReader<?> getBuilderOrNull(String sheetName) {
		return Iterables.tryFind(builders, 
			b->null != b && b.sheetConfig.getSheetName().equals(sheetName)).orNull();
	}
	@Override
	public void setSheetConfig(SheetConfig sheetConfig) {
		this.builders.get(0).setSheetConfig(sheetConfig);
	}

	@Override
	public SheetConfig getSheetConfig() {
		return this.builders.get(0).getSheetConfig();
	}

	@Override
	public void read(MultipartFile uplodFile, Charset charset) throws IOException {
	    try(InputStream inputStream = checkNotNull(uplodFile,"uplodFile is null").getInputStream()){
	    	log(firstNonNull(debugOutput,false),"UPLOAD FILE: {}",uplodFile.getOriginalFilename());
	    	read(inputStream, charset, suffixOf(uplodFile.getOriginalFilename()));
	    }		
	}

	@Override
	public void read(String file, Charset charset) throws IOException {
		try(FileInputStream inputStream = new FileInputStream(checkNotNull(file,"file is null"))) {
			log(firstNonNull(debugOutput,false),"FILE: {}",file);
			read(inputStream, charset, suffixOf(file));
		}
	}

	@Override
	public void read(File file, Charset charset) throws IOException {
		try(FileInputStream inputStream = new FileInputStream(checkNotNull(file,"file is null"))) {
			log(firstNonNull(debugOutput,false),"FILE: {}",file);
			read(inputStream, charset, suffixOf(file.getName()));
		}
	}
}
